package com.boss.fileapp.service;

import com.boss.fileapp.consts.FileActionType;
import com.boss.fileapp.dao.*;
import com.boss.fileapp.entity.CounterOffer;
import com.boss.fileapp.entity.FileSendLog;
import com.boss.fileapp.entity.Offer;
import com.boss.fileapp.util.*;
import com.boss.framework.exception.AppRuntimeException;
import com.boss.sys.utils.SysUtils;
import com.jcraft.jsch.ChannelSftp;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

@Service
public class OperateFileService {

    /**
     * 引入日志框架常量
     */
    private static final Logger logger = LoggerFactory.getLogger(OperateFileService.class);
    private static final String MX_SEPARATOR = "^";
    private static final String HZ_SEPARATOR = "\t";
    private static final String PARAM_SEPARATOR = ":";
    private static final String ZIP_SUFFIX = ".zip";
    private static final String SALARY_DATA_FILE = "salaryDataFile";
    private static final String EXCEPTION_CARD_NUMBER_FILE = "exceptionCardNumberFile";
    private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyyMMdd_HHmmss");


    @Value("${generateFilePath}")
    private String dirPath;

    private List<String> filePaths;
    private List<FileSendLog> logList;

    @Autowired
    private SftpCommonService sftpCommonService;
    @Autowired
    private FilePathConfigService filePathConfigService;
    @Autowired
    private FileSendLogService fileSendLogService;

    @Autowired
    private IBankOfferDataDao bankOfferDataDao;
    @Autowired
    private IFileSendLogDao fileSendLogDao;
    @Autowired
    private ISalaryBankPayBackDao salaryBankPayBackDao;
    @Autowired
    private IAgencyInfoDao agencyInfoDao;
    @Autowired
    private ISalaryBasedataDao salaryBasedataDao;
    @Autowired
    private IAgencyVerifyDao agencyVerifyDao;

    /**
     * 上传文件
      * @param sftp
     * @throws AppRuntimeException
     */
    public void uploadOfferFile(ChannelSftp sftp, String province,String sendBankCode) throws AppRuntimeException {
        //        上传之前先清空远端的报盘文件
        deleteRemoteHistoryFile(sftp, province,sendBankCode);
        List<String> overFileNameList = new ArrayList<>();
        List<String> successFilePathList = new ArrayList<>();
        InputStream inputStream = null;
        try {
            Map<String, String> pathMap = filePathConfigService.getBankCodeAndUploadDir(province,sendBankCode);
//            获取需要上传的文件
            List<FileSendLog> fileSendLogList = getDidNotSendFilePath(province,sendBankCode);
            for (FileSendLog fileSendLog : fileSendLogList) {
                String fileName = fileSendLog.getFileName();
                String filePath = dirPath + fileName;
                String fileActionType = fileSendLog.getFileActionType();
                String bankCode = fileSendLog.getSendBankCode();
//                读取文件
                byte[] bytes = Files.readAllBytes(Paths.get(filePath));
                inputStream = new ByteArrayInputStream(bytes);
//                判断当前银行是否有对应的文件保存目录路径
                if (!StringUtils.isEmpty(pathMap.get(bankCode))) {
//                上传文件
                    String overFileName = sftpCommonService.uploadFile(sftp, pathMap.get(bankCode), inputStream, fileName, fileActionType, bankCode);
                    successFilePathList.add(pathMap.get(bankCode) + overFileName);
                    overFileNameList.add(overFileName);
                } else {
                    throw new AppRuntimeException("存在无法判别所属银行的文件！");
                }
            }
            for (String overFileName : overFileNameList) {
//                删除已经上传完的文件
                Files.deleteIfExists(Paths.get(dirPath + overFileName));
//                修改文件上传状态
                FileSendLog overSendLog = new FileSendLog(overFileName);
                fileSendLogService.changeFileSendLog(overSendLog);
            }
        } catch (AppRuntimeException are) {
            logger.warn("上传文件发生异常: {}",are.getMessage());
            deleteUploadSuccessFile(sftp, successFilePathList);
            throw new AppRuntimeException(are);
        } catch (Exception e) {
            logger.warn("上传文件发生异常:",e);
            deleteUploadSuccessFile(sftp, successFilePathList);
            throw new AppRuntimeException("上传文件发生异常！");
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void deleteUploadSuccessFile(ChannelSftp sftp, List<String> successFilePathList) {
        for (String successFilePath : successFilePathList) {
            try {
                sftpCommonService.deleteFile(sftp, successFilePath);
            } catch (Exception e1) {
                logger.warn("报错后删除已上传成功的文件时发生异常！", e1);
                throw new AppRuntimeException("报错后删除已上传成功的文件时发生异常！");
            }
        }
    }

    /**
     * 生成报盘文件
     */
    public void generateOfferFile(String province,String sendBankCode,String dateTime) throws AppRuntimeException {

////        清空本地的报盘文件
        deleteLocalHistoryFile(province,sendBankCode);
//          生成16进制秘钥
        byte[] byteKey = AESUtils.generateAESKey();
        filePaths = new ArrayList<>();
        logList = new ArrayList<>();
        try{
            //生产报盘汇总文件
            logger.info(DateUtils.getCurrentTimeStr("yyyy-MM-dd HH:mm:ss")+"--"+province+"--"+"===========生成汇总报盘开始============");
            generateHZOfferFile(byteKey, province,sendBankCode,dateTime);
            logger.info(DateUtils.getCurrentTimeStr("yyyy-MM-dd HH:mm:ss")+"--"+province+"--"+"===========生成汇总报盘完成============");
            //生产报盘明细文件
            logger.info(DateUtils.getCurrentTimeStr("yyyy-MM-dd HH:mm:ss")+"--"+province+"--"+"===========生成明细报盘开始============");
            generateMXOfferFile(byteKey, province,sendBankCode,dateTime);
            logger.info(DateUtils.getCurrentTimeStr("yyyy-MM-dd HH:mm:ss")+"--"+province+"--"+"===========生成明细报盘完成============");
            fileSendLogDao.batchInsertFileSendLog(logList);
            List<String> fileNames = logList.stream().map(FileSendLog::getFileName).collect(Collectors.toList());
            ZipUtils.generateCompressFile(dirPath, fileNames, province +"_"+sendBankCode+ "_" + SALARY_DATA_FILE);
        }catch(AppRuntimeException e){
//            生成失败则删除刚生成的文件
            deleteCreateSuccessFile();
            logger.warn("生成文件发生异常: {}",e.getMessage());
            throw e;
        }catch(Exception e){
//            生成失败则删除刚生成的文件
            deleteCreateSuccessFile();
            logger.warn("生成文件发生异常:",e);
            throw new AppRuntimeException("生成文件发生异常！");
        }

    }

    /**
     * 报错后删除已经生成的本地文件
     */
    private void deleteCreateSuccessFile() {
        try {
            for(String filePath : filePaths){
                Path path = Paths.get(filePath);
                Files.deleteIfExists(path);
            }
        } catch (IOException e) {
            logger.warn("生成文件报错时删除已生成的文件发生异常！", e);
            throw new AppRuntimeException("生成文件报错时删除已生成的文件发生异常！");
        }
    }

    /**
     * 生成文件前，将之前上传的远端文件删除
     */
    public void deleteRemoteHistoryFile(ChannelSftp sftp, String province,String sendBankCode){
        Map<String, String> pathMap = filePathConfigService.getBankCodeAndUploadDir(province,sendBankCode);
        List<String> dirList = new ArrayList<>(pathMap.values());
        for(String dir : dirList){
            if(sftpCommonService.isDirExist(sftp, dir)){
//                远端存在该目录
                try {
                    List<String> fileNameList = sftpCommonService.getFileNames(sftp, dir);
                    if(!CollectionUtils.isEmpty(fileNameList)){
                        for(String fileName : fileNameList){
                            if(!fileName.startsWith(".")){
                                sftpCommonService.deleteFile(sftp, dir + fileName);
                            }
                        }
                    }
                } catch (Exception e) {
                    logger.warn("获取远端文件名集合失败或删除文件失败！",e);
                    throw new AppRuntimeException("获取远端文件名集合失败或删除文件失败！");
                }
            }else{
                logger.warn("配置文件中的远端文件路径配置错误！{}",dir);
                throw new AppRuntimeException("配置文件中的远端文件路径配置错误！");
            }

        }
    }

    /**
     * 生成文件前，将之前生成的本地文件删除
     */
    public void deleteLocalHistoryFile(String province,String sendBankCode){
        List<String> fileList = fileSendLogDao.selectFileNameByProvince(province,sendBankCode);
        try {
            for (String fileName : fileList) {
                Path filePath = Paths.get(this.dirPath + fileName);
                Files.deleteIfExists(filePath);
            }
            Path salaryDataFilePath = Paths.get(this.dirPath + province + "_" + SALARY_DATA_FILE + ".zip");
            Files.deleteIfExists(salaryDataFilePath);
            Path exceptionCardNumberFilePath = Paths.get(this.dirPath + province + "_" + EXCEPTION_CARD_NUMBER_FILE + ".zip");
            Files.deleteIfExists(exceptionCardNumberFilePath);
        } catch (IOException e) {
            logger.warn("删除之前生成的报盘文件发生异常！",e);
            throw new AppRuntimeException("删除之前生成的报盘文件发生异常！");
        }
        fileSendLogDao.physicalDeleteHistoryLog(province,sendBankCode);
    }

    /**
     * 生成工资数据：生成汇总报盘文件
     * @param byteKey //16进制秘钥
     * @throws AppRuntimeException
     * @throws IOException
     */
    public void generateHZOfferFile(byte[] byteKey, String province,String sendBankCode,String dateTime) throws AppRuntimeException, IOException {
        List<Offer> offerList = bankOfferDataDao.selectHZOffer(province,sendBankCode);
        List<String> successList=bankOfferDataDao.selectSuccessPersonIden();
        Map<String,Offer>offerMap=new HashMap();
        for(Offer offer:offerList){
            offerMap.put(offer.getIdenNo(),offer);
        }
        for(String key:offerMap.keySet()){
            if(successList.contains(key)){
                offerList.remove(offerMap.get(key));
            }
        }

        createAndResolveHZOfferFile(byteKey, province, offerList,dateTime,sendBankCode);
    }

    /**
     * 异常卡号：生成汇总报盘文件
     * @param byteKey
     * @param personIds
     * @throws AppRuntimeException
     * @throws IOException
     */
    public void generateHZOfferFile(byte[] byteKey, List<String> personIds, String province,String dateTime) throws AppRuntimeException, IOException {
        List<Offer> offerList = salaryBankPayBackDao.selectHZOffer(personIds, province);
        if(!CollectionUtils.isEmpty(offerList)){
            createAndResolveHZOfferFile(byteKey, province, offerList,dateTime,null);
        }else{
            logger.info("没有需要生成工资数据文件的异常卡号！");
            throw new AppRuntimeException("没有需要生成工资数据文件的异常卡号！");
        }
    }

    /**
     * 生成汇总报盘文件
     * @byteKey 16进制密钥
     */
    public void createAndResolveHZOfferFile(byte[] byteKey, String province, List<Offer> offerList,String ymdhms,String sendBankCode) throws AppRuntimeException, IOException {
        if(!CollectionUtils.isEmpty(offerList)){
          List<Offer> personCounts= bankOfferDataDao.selectAgencyBankPersonCount(province,sendBankCode);
            //统发人数map
            Map<String,List<Offer>> personCountMap=personCounts.stream().collect(Collectors.groupingBy(Offer::getSalaCardBank));
//          处理数据
            Map<String, List<Offer>> bankJoinOfferMap = offerList.stream().collect(Collectors.groupingBy(Offer::getSalaCardBank));
            for(String bankCode : bankJoinOfferMap.keySet()){
                List<Offer> offers = bankJoinOfferMap.get(bankCode);
                List<Offer> personCountoffers = personCountMap.get(bankCode);
                Map<String,Offer> personCountOfferMap=personCountoffers.stream().collect(Collectors.toMap(Offer::getAgencyCode, Function.identity(), (key1, key2) -> key2));
                StringBuilder sb = new StringBuilder();
                for(Offer offer : offers){
                    sb.append(offer.getId()).append(HZ_SEPARATOR)
                            .append(offer.getBizKey()).append(HZ_SEPARATOR)
                            .append(offer.getIdenNo()).append(HZ_SEPARATOR)
                            .append(offer.getPersonName()).append(HZ_SEPARATOR)
                            .append(offer.getRealSalary()).append(HZ_SEPARATOR)
                            .append(offer.getSalaCardNo()).append(HZ_SEPARATOR)
                            .append(offer.getSalaryMonth()).append(HZ_SEPARATOR)
                            .append(offer.getAgencyCode()).append(HZ_SEPARATOR)
                            .append(offer.getAgencyName()).append(HZ_SEPARATOR)
                            .append(personCountOfferMap.get(offer.getAgencyCode()).getPersonCount())
                            .append("\n");
                }
////                文件生成时间
//                Date date = new Date();
//                String ymdhms = SDF.format(date);
//                生成汇总文件
                String HZFileName = createAndEncryptOffer(sb.toString().getBytes(), FileActionType.HZ_PREFIX, bankCode, byteKey, ymdhms);
                filePaths.add(dirPath + HZFileName);
                FileSendLog fileSendLog = new FileSendLog(HZFileName, bankCode, FileActionType.HZ_PREFIX, province);
//                生成MAC值并保存到秘钥文件中
                String HZMACValue = generateMACValue(sb.toString().getBytes(), byteKey);
                String keyFileName = createKeyFile(FileActionType.SY_HZ_PREFIX, bankCode, byteKey, HZMACValue, ymdhms);
                filePaths.add(dirPath + keyFileName);
                FileSendLog keyFileSendLog = new FileSendLog(keyFileName, bankCode, FileActionType.SY_HZ_PREFIX, province);
                //保存文件生成日志
                logList.add(fileSendLog);
                logList.add(keyFileSendLog);
            }
        }
    }

    /**
     * 生成并加密报盘文件
     * @param context 文件内容
     * @param filePrefix 文件名称格式回盘头RE
     * @param bankCode 银行编码
     * @param byteKey  16进制密钥
     * @return
     * @throws IOException
     */
    private String createAndEncryptOffer(byte[] context, String filePrefix, String bankCode, byte[] byteKey, String ymdhms) throws IOException {
        String fileName = FileUtils.createFileName(filePrefix, bankCode, ymdhms);
        String filePath = dirPath + fileName;
//        生成临时明文文件
        String tempFilePath = dirPath + "temp_" + fileName;
        Path tempFile = generateFileAndGetPath(context, tempFilePath);
//        加密文件，生成目标文件
        String sKey = MACUtils.byte2Hex(byteKey);
        AESUtils.encryptFile(tempFile.toFile(), filePath, sKey);
//        删除临时文件
        Files.deleteIfExists(tempFile);
        return fileName;
    }

    /**
     * 生成报盘临时文件
     * @param context 文件内容
     * @param targetFilePath 临时文件路径
     * @return
     * @throws IOException
     */
    private Path generateFileAndGetPath(byte[] context, String targetFilePath) throws IOException {
        Path dir = Paths.get(dirPath);
        Path targetFile = Paths.get(targetFilePath);
//        如果上级目录不存在则创建
        if(!Files.exists(dir)){
            Files.createDirectories(dir);
            Files.createFile(targetFile);
        }
        Files.write(targetFile, context);
        return targetFile;
    }

    /**
     * 生成MAC值
     * @param context 文件内容
     * @param byteKey 16进制密钥
     * @return
     */
    private String generateMACValue(byte[] context, byte[] byteKey){
        byte[] keyByte = MACUtils.CalcPBOCMacX919(byteKey, context);
        if(keyByte == null){
            throw new AppRuntimeException("生成的秘钥有误！keyByte的长度不等于16");
        }
        return MACUtils.byte2Hex(keyByte);
    }

    /**
     * 生成秘钥文件
     * @param filePrefix
     * @param bankCode
     * @param byteKey
     * @param MACValue
     * @return
     * @throws IOException
     */
    private String createKeyFile(String filePrefix, String bankCode, byte[] byteKey, String MACValue, String ymdhms) throws IOException {
        String keyFileName = FileUtils.createFileName(filePrefix, bankCode, ymdhms);
        String keyFilePath = dirPath + keyFileName;
        String sKey = MACUtils.byte2Hex(byteKey);
        String context = sKey + '\n' + MACValue;
        generateFileAndGetPath(context.getBytes(), keyFilePath);
        return keyFileName;
    }

    /**
     * 生成明细报盘文件
     * @param byteKey
     * @throws AppRuntimeException
     * @throws IOException
     */
    public void generateMXOfferFile(byte[] byteKey, String province,String sendBankCode,String ymdhms) throws AppRuntimeException, IOException {
//        工资编码list
        List<Map<String,Object>> salaryCodeNameList = salaryBasedataDao.selectSalaryCodeByProvince(province);
        List<String> salaryCodeList = new ArrayList<>();
        List<String> salaryNameList = new ArrayList<>();
        for(Map<String,Object> salaryMap:salaryCodeNameList){
            salaryCodeList.add(salaryMap.get("salary_code").toString());
            salaryNameList.add(salaryMap.get("salary_name").toString());
        }
//        获取全部机构id（通过该ids获取该机构挂接的工资项）
        List<String> agencyIds = agencyInfoDao.selectAgencyIdsByProvince(province,sendBankCode);
        if(CollectionUtils.isEmpty(agencyIds)){
            throw new AppRuntimeException("没有可以生成工资数据的机构，可能是机构中没有人员。");
        }
        Map<String, StringBuilder> bankJoinOfferMap = new HashMap<>();

           // List<AgencySalary> agencySalaryList = agencySalaryDao.selectColumnName(agencyId, salaryCodeList);
           // List<String> currentColumnList = agencySalaryList.stream().map(AgencySalary::getColumnName).collect(Collectors.toList());
            List<Map<String,Object>> list = bankOfferDataDao.selectMXOffer(agencyIds, salaryCodeList);
            if(!CollectionUtils.isEmpty(list)){
                for(Map<String, Object> offer : list){
                    String salaCardBank = String.valueOf(offer.get("salaCardBank"));
                    if(bankJoinOfferMap.containsKey(salaCardBank)){
//                        包含当前银行编码
                        StringBuilder sb = bankJoinOfferMap.get(salaCardBank);
                        concatMXContent(salaryCodeList, offer, sb);
                    }else{
//                        不包含当前银行编码
                        StringBuilder sb = new StringBuilder();
                        //不含当前银行说明是第一条数据，拼接表头信息
                        concatMXHeadContent(salaryNameList,sb);
                        concatMXContent(salaryCodeList, offer, sb);
                        bankJoinOfferMap.put(salaCardBank, sb);
                    }
                }
            }

        for(String bankCode : bankJoinOfferMap.keySet()){
            StringBuilder content = bankJoinOfferMap.get(bankCode);
//            生成明细文件
            String MXFileName = createAndEncryptOffer(content.toString().getBytes(), FileActionType.MX_PREFIX, bankCode, byteKey, ymdhms);
            filePaths.add(dirPath + MXFileName);
            FileSendLog fileSendLog = new FileSendLog(MXFileName, bankCode, FileActionType.MX_PREFIX, province);
//            生成MAC值并保存到秘钥文件中
            String MXMACValue = generateMACValue(content.toString().getBytes(), byteKey);
            String keyFileName = createKeyFile(FileActionType.SY_MX_PREFIX, bankCode, byteKey, MXMACValue, ymdhms);
            filePaths.add(dirPath + keyFileName);
            FileSendLog keyFileSendLog = new FileSendLog(keyFileName, bankCode, FileActionType.SY_MX_PREFIX, province);
            //保存文件生成日志
            logList.add(fileSendLog);
            logList.add(keyFileSendLog);
        }
    }


    /**
     * 生成异常卡号明细报盘文件
     * @param byteKey
     * @throws AppRuntimeException
     * @throws IOException
     */
    public void generateBankPayBackMXOfferFile(byte[] byteKey, String province,String sendBankCode, List<String> personIds,String ymdhms) throws AppRuntimeException, IOException {
//        工资编码list
        List<Map<String,Object>> salaryCodeNameList = salaryBasedataDao.selectSalaryCodeByProvince(province);
        List<String> salaryCodeList = new ArrayList<>();
        List<String> salaryNameList = new ArrayList<>();
        for(Map<String,Object> salaryMap:salaryCodeNameList){
            salaryCodeList.add(salaryMap.get("salary_code").toString());
            salaryNameList.add(salaryMap.get("salary_name").toString());
        }
//        获取全部机构id（通过该ids获取该机构挂接的工资项）
        List<String> agencyIds = agencyInfoDao.selectAgencyIdsByProvince(province,sendBankCode);
        if(CollectionUtils.isEmpty(agencyIds)){
            throw new AppRuntimeException("没有可以生成工资数据的机构，可能是机构中没有人员。");
        }
        Map<String, StringBuilder> bankJoinOfferMap = new HashMap<>();
        for(String agencyId : agencyIds){
            // List<AgencySalary> agencySalaryList = agencySalaryDao.selectColumnName(agencyId, salaryCodeList);
            // List<String> currentColumnList = agencySalaryList.stream().map(AgencySalary::getColumnName).collect(Collectors.toList());
            List<Map<String,Object>> list = bankOfferDataDao.selectBankPayBackMXOffer(agencyId, salaryCodeList,personIds);
            if(!CollectionUtils.isEmpty(list)){
                for(Map<String, Object> offer : list){
                    String salaCardBank = String.valueOf(offer.get("salaCardBank"));
                    if(bankJoinOfferMap.containsKey(salaCardBank)){
//                        包含当前银行编码
                        StringBuilder sb = bankJoinOfferMap.get(salaCardBank);
                        concatMXContent(salaryCodeList, offer, sb);
                    }else{
//                        不包含当前银行编码
                        StringBuilder sb = new StringBuilder();
                        //不含当前银行说明是第一条数据，拼接表头信息
                        concatMXHeadContent(salaryNameList,sb);
                        concatMXContent(salaryCodeList, offer, sb);
                        bankJoinOfferMap.put(salaCardBank, sb);
                    }
                }
            }
        }
        for(String bankCode : bankJoinOfferMap.keySet()){
            StringBuilder content = bankJoinOfferMap.get(bankCode);
////            文件生成时间
//            Date date = new Date();
//            String ymdhms = SDF.format(date);
//            生成明细文件
            String MXFileName = createAndEncryptOffer(content.toString().getBytes(), FileActionType.MX_PREFIX, bankCode, byteKey, ymdhms);
            filePaths.add(dirPath + MXFileName);
            FileSendLog fileSendLog = new FileSendLog(MXFileName, bankCode, FileActionType.MX_PREFIX, province);
//            生成MAC值并保存到秘钥文件中
            String MXMACValue = generateMACValue(content.toString().getBytes(), byteKey);
            String keyFileName = createKeyFile(FileActionType.SY_MX_PREFIX, bankCode, byteKey, MXMACValue, ymdhms);
            filePaths.add(dirPath + keyFileName);
            FileSendLog keyFileSendLog = new FileSendLog(keyFileName, bankCode, FileActionType.SY_MX_PREFIX, province);
            //保存文件生成日志
            logList.add(fileSendLog);
            logList.add(keyFileSendLog);
        }
    }

    /**
     * 拼接明细文件内容
     * @param currentColumnList
     * @param offer
     * @param sb
     */
    private void concatMXContent(List<String> currentColumnList, Map<String, Object> offer, StringBuilder sb) {
        Map<String,String> meritPay=new HashMap<>();
        meritPay.put("0","行政");
        meritPay.put("1","事业");
        meritPay.put("11","离退休");
        String meritPayCode=StringUtils.isEmpty(meritPay.get(offer.get("meritPayCode")))?offer.get("meritPayCode").toString():meritPay.get(offer.get("meritPayCode"));
        sb.append("D").append(MX_SEPARATOR)
                .append(offer.get("agencyCode")).append(MX_SEPARATOR)
                .append(offer.get("agencyName")).append(MX_SEPARATOR)
                .append(meritPayCode).append(MX_SEPARATOR)
                .append(offer.get("deptId")).append(MX_SEPARATOR)
                .append(offer.get("showOrder")).append(MX_SEPARATOR)
                .append(offer.get("personCode")).append(MX_SEPARATOR)
                .append(offer.get("salaCardNo")).append(MX_SEPARATOR)
                .append(offer.get("personName")).append(MX_SEPARATOR)
                .append(offer.get("idenNo")).append(MX_SEPARATOR)
                .append(offer.get("ns1")).append(MX_SEPARATOR)
                .append(offer.get("ns2")).append(MX_SEPARATOR);
        for(int i = 0; i < currentColumnList.size(); i++){
            String column = currentColumnList.get(i);
            if("0".equals(column)){
                sb.append(0);
            }else{
                sb.append(offer.get(column));
            }
            if(i != currentColumnList.size() - 1){
                 sb.append(MX_SEPARATOR);
            }
        }
        sb.append("\n");
    }

    /**
     * 拼接明细文件表头内容
     * @param salaryName
     * @param sb
     */
    private void concatMXHeadContent( List<String> salaryName, StringBuilder sb) {

                sb.append("D").append(MX_SEPARATOR)
                .append("单位编码").append(MX_SEPARATOR)
                .append("单位名称").append(MX_SEPARATOR)
                .append("工资性质名称").append(MX_SEPARATOR)
                .append("部门编码").append(MX_SEPARATOR)
                .append("级别内排序序号").append(MX_SEPARATOR)
                .append("人员编码").append(MX_SEPARATOR)
                .append("工资卡号").append(MX_SEPARATOR)
                .append("姓名").append(MX_SEPARATOR)
                .append("身份证号码").append(MX_SEPARATOR)
                .append("实发工资").append(MX_SEPARATOR)
                .append("应发工资").append(MX_SEPARATOR);
        for(int i = 0; i < salaryName.size(); i++){
             sb.append(salaryName.get(i));
            if(i != salaryName.size() - 1){
                sb.append(MX_SEPARATOR);
            }
        }
        sb.append("\n");
    }

    /**
     * 获取需要上传的文件名
     * @return
     */
    public List<FileSendLog> getDidNotSendFilePath(String province,String sendBankCode){
        Map<String, Object> conditions = new HashMap<>();
        conditions.put("isSend", 0);
        conditions.put("mofDivCode", province);
        conditions.put("sendBankCode", sendBankCode);//上传文件银行编码
        List<FileSendLog> logList = fileSendLogDao.selectByConditions(conditions);
        if(CollectionUtils.isEmpty(logList)){
            throw new AppRuntimeException("没有可上传的文件！");
        }else{
            return logList;
        }
    }

    /**
     * 解析回盘秘钥文件
     * @param sftp
     * @param dirPath
     * @param SYFileName
     * @return
     */
    public String[] analysisSYFile(ChannelSftp sftp, String dirPath, String SYFileName){
        try(InputStream is = sftp.get(dirPath + SYFileName);
            BufferedInputStream bis = new BufferedInputStream(is)){
            StringBuilder content = new StringBuilder();
            int len;
            byte[] bytes = new byte[1024 * 10];
            while((len = bis.read(bytes)) != -1){
                content.append(new String(bytes, 0, len));
            }
            return content.toString().split("\n");
        }catch(Exception e){
            logger.warn("解析私钥文件时发生异常！", e);
            throw new AppRuntimeException("解析私钥文件时发生异常！");
        }
    }

    /**
     * 解析回盘汇总文件 返回异常人数
     * @param sftp
     * @param sftpDir
     * @param REHZFileName
     * @param sKey
     * @param MACValue
     */
    public int analysisOfferFile(ChannelSftp sftp, String sftpDir, String REHZFileName,
                                             String sKey, String MACValue, String province){
        String localDir = this.dirPath;
        int i=0;
        try {
            sftpCommonService.download(sftp, sftpDir, localDir, REHZFileName);
            File REHZFile = new File(localDir + REHZFileName);
//            明文文件名称
            String clearPath = dirPath + "temp_" + REHZFileName;
            AESUtils.decryptFile(REHZFile, clearPath, sKey);
            Files.deleteIfExists(REHZFile.toPath());
//            读取明文文件
            byte[] content = Files.readAllBytes(Paths.get(clearPath));
//            校验MAC值
            String localMACValue = generateMACValue(content, MACUtils.hex2byte(sKey.getBytes()));
            if(!MACValue.equals(localMACValue)){
                logger.warn("[{}]文件MAC校验失败！远端为[{}],本地为[{}].", REHZFileName, MACValue, localMACValue);
                Files.deleteIfExists(Paths.get(clearPath));
                throw new AppRuntimeException("文件MAC校验失败！");
            }
//            保存数据到异常卡号表
            String data = new String(content, StandardCharsets.UTF_8);

            //查询已回盘数据
            List<CounterOffer> backedList= salaryBankPayBackDao.selectBankPayBack(province);
            Map<String,String> counterOfferMap=new HashMap<>();
            for(CounterOffer counterOffer:backedList){//转换成Map通过旧卡号和月份id联合唯一主键做key
                counterOfferMap.put(counterOffer.getOldSalaCardNo()+counterOffer.getPersonName()+counterOffer.getMonthId(),"1");
            }
            List<String> idens=new ArrayList<>();
            List<CounterOffer> list = new ArrayList<>();
            for(String info : data.split("\n")){
                String[] params = info.split("\t");
//                给counterOffer赋属性值
                CounterOffer counterOffer = new CounterOffer();
                counterOffer.setId(SysUtils.generateGuid());
                counterOffer.setMonthId(params[0]);
                counterOffer.setBizKey(params[1]);
                counterOffer.setUpdateTime(DateUtils.getCurrentTimeStr(null));
                counterOffer.setCreateTime(DateUtils.getCurrentTimeStr(null));
                counterOffer.setIdenNo(params[2]);
                counterOffer.setPersonName(params[3]);
                counterOffer.setRealSalary(Double.valueOf(params[4]));
                counterOffer.setOldSalaCardNo(params[5]);
                counterOffer.setBankBackCode(params[6]);
                counterOffer.setBankBackRemark(params[7]);
                counterOffer.setIsUpdate(0);
                counterOffer.setIsRecreate(0);
                counterOffer.setIsUpdatePerson(0);
                counterOffer.setIsSend(0);
                counterOffer.setMofDivCode(province);
                idens.add(params[2]);
                if(StringUtils.isEmpty( counterOfferMap.get(counterOffer.getOldSalaCardNo()+counterOffer.getPersonName()+counterOffer.getMonthId()))) {
                    //若通过唯一主键未找到数据，则代表是新的数据
                    list.add(counterOffer);
                    if("F".equals(counterOffer.getBankBackCode())){
                        i++;
                    }
                }
            }
            //20210508新增messageMap查询人员id和agencyId
            Map<String,Map<String,Object>> messageMap= getMessageMap(idens);
            if(!CollectionUtils.isEmpty(list)){
                salaryBankPayBackDao.batchReplaceCounterOffer(list,messageMap);
                Files.deleteIfExists(Paths.get(clearPath));
            }
            return i;
        } catch (AppRuntimeException e) {
            logger.warn("处理汇总回盘文件发生异常: {}",e.getMessage());
        } catch (Exception e) {
            logger.warn("处理文件发生异常",e);
        }
        return -1;
    }

    /**
     * 生成并上传报盘工资文件（异常卡号人员）
     * @param sftp
     * @param personIds
     */
    public void generateAndUploadBankPayBackFile(ChannelSftp sftp, List<String> personIds, String province,String ymdhms){
        if(CollectionUtils.isEmpty(personIds)){
            throw new AppRuntimeException("没有需要生成的数据。");
        }
//        清空远端的报盘文件
        deleteRemoteHistoryFile(sftp, province,null);
//        清空本地的报盘文件
        deleteLocalHistoryFile(province,null);
//          生成16进制秘钥
        byte[] byteKey = AESUtils.generateAESKey();
        filePaths = new ArrayList<>();
        logList = new ArrayList<>();
        try{
            //异常卡号汇总回盘
            generateHZOfferFile(byteKey, personIds, province,ymdhms);
            //异常卡号明细回盘
            generateBankPayBackMXOfferFile(byteKey, province,null,personIds,ymdhms);
            fileSendLogDao.batchInsertFileSendLog(logList);
            List<String> fileNames = logList.stream().map(FileSendLog::getFileName).collect(Collectors.toList());
            ZipUtils.generateCompressFile(dirPath, fileNames, province + "_" + EXCEPTION_CARD_NUMBER_FILE);
            uploadOfferFile(sftp, province,null);
        }catch(AppRuntimeException e){
//        清空远端的报盘文件
            deleteRemoteHistoryFile(sftp, province,null);
//        清空本地的报盘文件
            deleteLocalHistoryFile(province,null);
            logger.warn("生成异常卡号人员工资文件发生异常: {}",e.getMessage());
            throw e;
        }catch(Exception e){
//        清空远端的报盘文件
            deleteRemoteHistoryFile(sftp, province,null);
//        清空本地的报盘文件
            deleteLocalHistoryFile(province,null);
            logger.warn("生成异常卡号人员工资文件发生异常:",e);
            throw new AppRuntimeException("生成异常卡号人员工资文件发生异常！");
        }
    }

   public Map<String,Map<String,Object>> getMessageMap(List<String> idens){
       Map<String,Map<String,Object>> messageMap=new HashMap<>();
       Map<String,Object> personIdMap=new HashMap<>();
       Map<String,Object> personAgencyIdMap=new HashMap<>();
       List<Map<String,Object>> personExts= agencyVerifyDao.getPersonExtByIdenNo(idens);
       for(Map<String,Object> map:personExts){
           personIdMap.put(map.get("iden_no").toString(),map.get("person_id"));
           personAgencyIdMap.put(map.get("iden_no").toString(),map.get("agency_id"));
       }
       messageMap.put("personIdMap",personIdMap);
       messageMap.put("personAgencyIdMap",personAgencyIdMap);
        return messageMap;
    }

}
